---------------------------------------	制作说明	---------------------------------------
--[[
		插件作者	:	山海师、小玮、神梦无痕、天纵少侠
		制作时间	:	2016.03.04
		联系 Q Q	:	1915025270
		交流QQ群	:	153263411
		
		本插件稳定每周更新，发现BUG或有新的功能需求请联系作者（联系方式往上看↑_↑）
--]]


QMPlugin = {}

--
---------------------------------------------- 全局数据 ----------------------------------------------
--

local SHver			= 2.7			-- 对外版本号
local Ver_LastUpd	= 20170815		-- 内部版本号 [即最后更新日期]



--
---------------------------------------------- 内部函数 ----------------------------------------------
--

-- 按键精灵缓存目录的上级目录 [无参数] [返回临时文件目录路径, 失败返回空字符串]
function TempPath()
	local iRet, sRet = pcall(function()
		return __MRUNNER_LOCAL_PATH_GLOBAL_NAME__
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end
function ScanTable(tbl)
    local ret = ""
    for key, value in pairs(tbl) do
        if (key ~= "_G") and (key ~= "package") then
            if type(value) == "table" then
                ret = ret .. "\r\n" .. key .. ScanTable(value)
            elseif type(value) == "function" then
                ret = ret .. "\r\n" .. key .. "(" .. type(value) .. ")"
            elseif type(value) == "userdata" then
                ret = ret .. "\r\n" .. key .. "(" .. type(value) .. ")"
            else
                ret = ret .. "\r\n" .. key .. "=" .. value .. "(" .. type(value) .. ")"
            end
        end
    end
    return ret
end

function QMPlugin.test()
    return ScanTable(_G)
end

-- 临时文件路径 [fn:文件名] [返回文件路径, 失败返回空字符串]
function TempFile(fn)
	local tPath = TempPath()
	if tPath == "" then
		return ""
	else
		if fn == nil then
			return GetTempFile(tPath, "shtemp_", ".txt", 5)
		else
			return tPath .. fn .. ".txt"
		end
	end
end

-- 复制表 [old:旧表] [返回复制表]
function CopyTable(old)
	local NewTable = {}
	for k, v in pairs(old) do
		NewTable[k] = v
	end
	return NewTable
end

-- 复制数组 [old:旧数组] [返回复制数组]
function CopyArray(old)
	local NewArray = {}
	for k, v in ipairs(old) do
		NewArray[k] = v
	end
	return NewArray
end

function print(str)
	LuaAuxLib.TracePrint(":", str)
end

function match(str,format)
	return str:match(format)
end
function gsub(str,f,s)
	return str:gsub(f,s)
end

function sh_init()
	
end










--
---------------------------------------------- 字符串函数 ----------------------------------------------
--

-- 过滤前导字符 [str:要处理的字符串, filt:要过滤的字符]
function LTrimEx(str, filt)
	sh_init()
	local iRet, sRet = pcall(function()
		local retstr = ""
		for i = 1, string.len(str) do
			if string.find(filt, string.sub(str, i, i), 1, true) == nil then
				retstr = string.sub(str, i, -1)
				break
			end
		end
		return retstr
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 过滤后导字符 [str:要处理的字符串, filt:要过滤的字符]
function RTrimEx(str, filt)
	sh_init()
	local iRet, sRet = pcall(function()
		local retstr = ""
		for i = string.len(str), 1, -1 do
			if string.find(filt, string.sub(str, i, i), 1, true) == nil then
				retstr = string.sub(str, 1, i)
				break
			end
		end
		return retstr
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 过滤前导与后导字符 [str:要处理的字符串, filt:要过滤的字符]
function TrimEx(str, filt)
	sh_init()
	local tmpstr
	tmpstr = LTrimEx(str, filt)
	return RTrimEx(tmpstr, filt)
end

-- 删除字符中间的一段 [str:要处理的字符串, sval:删除开始位置, eval:删除结束位置]
function QMPlugin.DelPartStr(str, sval, eval)
	sh_init()
	local iRet, sRet = pcall(function()
		local LStr = string.sub(str, 1, sval-1)
		local RStr = string.sub(str, eval+1, -1)
		local RetStr = LStr ..RStr
		return RetStr
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 删除指定字符 [str:要处理的字符串, filter:要删除的字符]
function QMPlugin.DelFilStr(str, filter)
	sh_init()
	local iRet, sRet = pcall(function()
		local RetStr, TmpLStr, TmpRStr, s, e
		RetStr = str
		while true do
			s, e = string.find(RetStr, filter, e)
			if s~= nil then
				TmpLStr = string.sub(RetStr, 1, s-1)
				TmpRStr = string.sub(RetStr, e+1, -1)
				RetStr = TmpLStr ..TmpRStr
			else
				break
			end
		end
		return RetStr
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end
 
-- 统计指定字符数量 [str:要处理的字符串, substr:要查找的子串]
function QMPlugin.CountStr(str, substr)
	sh_init()
	local iRet, sRet = pcall(function()
		local count = 0
		for k in string.gmatch(str, substr) do
			count = count +1
		end
		return count
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 从左侧删除指定字符 [str:要处理的字符串, num:要删除的数量] [返回删除后的字符]
function QMPlugin.LeftDel(str, num)
	sh_init()
	local iRet, sRet = pcall(function()
		return str:sub(num + 1, -1)
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 从右侧删除指定字符 [str:要处理的字符串, num:要删除的数量] [返回删除后的字符]
function QMPlugin.RightDel(str, num)
	sh_init()
	local iRet, sRet = pcall(function()
		return str:sub(1, str:len() - num)
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

--提取路径中包含的文件后缀
function QMPlugin.GetFileType(path)
	sh_init()
	local iRet, sRet = pcall(function()
		s,e = string.find(path,"%.")
		local str = string.sub(path,s+1 ,-1)
		if str then
			return str
		else
			return ""
		end
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end 

--提取路径中包含的文件名 
function GetFileName(path)
	sh_init()
	local iRet, sRet = pcall(function()
		path = "/" .. path
		local ret
		for w in string.gmatch(path, "/([^/]+)") do
			ret = w
		end
		return ret
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end
QMPlugin.GetFileName = GetFileName











--
---------------------------------------------- 数组函数 ----------------------------------------------
--


-- 数组排序 [arr:数组, comp:是否重构数组索引] [返回排序后的数组]
function QMPlugin.ArraySort(arr, comp)
	sh_init()
	local iRet, sRet = pcall(function()
		local t = {}
		local j = 1
		tarr = CopyTable(arr)
		table.sort(tarr)
		if comp then
			for i = #tarr, 1, -1 do
				t[j] = tarr[i]
				j = j + 1
			end
			return t
		else
			return tarr
		end
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return arr
	end
end

-- 删除数组元素 [arr:数组, ipos:删除元素的位置] [返回操作后的数组]
function QMPlugin.ArrayRemove(arr, ipos)
	sh_init()
	local iRet, sRet = pcall(function()
		tarr = CopyTable(arr)
		table.remove(tarr, ipos+1)
		return tarr
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return arr
	end
end

-- 插入数组元素 [arr:数组, ipos:删除元素的位置, value:插入的值] [返回操作后的数组]
function QMPlugin.ArrayInsert(arr, ipos, value)
	sh_init()
	local iRet, sRet = pcall(function()
		tarr = CopyTable(arr)
		table.insert(tarr, ipos+1, value)
		return tarr
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return arr
	end
end

-- 数组去重 [tbl:数组或表] [返回操作后的数组]
function QMPlugin.ArrayRemoveDup(tbl)
	sh_init()
	local iRet, sRet = pcall(function()
		local a = {}
		local b = {}
		for _, v in ipairs(tbl) do
			a[v] = 0
		end
		for k, v in pairs(a) do
			table.insert(b, k)
		end
		return b
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 数组元素改变位置 [arr:数组或表, a:交换位置的第一个元素, b:第二个元素] [返回操作后的数组]
function QMPlugin.ArrayChangePos(arr, a, b)
	sh_init()
	local iRet, sRet = pcall(function()
		local tmptbl = arr
		local tmpval = arr[a+1]
		table.remove(tmptbl, a+1)
		table.insert(tmptbl, b+1, tmpval)
		return tmptbl
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end











--
---------------------------------------------- 随机数函数 ----------------------------------------------
--

--随机取逻辑值
function QMPlugin.RndBool()
	sh_init()
	local iRet, sRet = pcall(function()
		local tmpnum = math.random(2)-1
		if tmpnum == 0 then
			return true
		else 
			return false
		end
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end 

--随机取数组内容 
function QMPlugin.RandArray(arr)
	sh_init()
	local iRet, sRet = pcall(function()
		local index
		index = math.random(1,#arr)
		return arr[index]
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end 

--随机生成不重复数字  [作者：小玮]
function QMPlugin.RandArrayEx(arr,num)
	sh_init()
	local iRet, sRet = pcall(function()
		if num == nil or num > #arr then num = #arr end
		local Lines = {}
		for _ = 1,num do
			local n = math.random(#arr)
			Lines[#Lines + 1] = arr[n]
			table.remove(arr,n)
		end
		return Lines
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 生成随机汉字
function QMPlugin.RndChr()
	sh_init()
	local iRet, sRet = pcall(function()
		local hight = math.random(16, 55) + 160
		local low = math.random(1, 94) + 160
		hight = string.format("%#x", hight)
		low = string.format("%#x",low)
		return string.char(hight, low)
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end












--
---------------------------------------------- 网页日志函数 ----------------------------------------------
--



local Html = {}
local isAutoSave = true
local isInit = true
local HtmlData = ""
local HtmlPage = ""

function Init()
	local iRet, sRet = pcall(function()
	-- 0=白色、1=绿色、2=橙色、3=红色
	if Html.HtmlLevel == nil then
		Html.HtmlLevel = 1
	end
	-- 日志模版
	if Html.TempletItem == nil then
		Html.TempletItem = "<p class='class{$level}'>{$data}</p>"
	end
	-- 日志路径
	if Html.HtmlPath == nil then
		Html.HtmlPath = TempPath().."log_"..os.date("%Y%m%d%H%M")..".html"
	end
	-- 日志标题
	if Html.HtmlTitle == nil then
		Html.HtmlTitle = "日志记录系统"
	end
	-- 网页模版
	if Html.TempletPage == nil then
		Html.TempletPage = [[
		<html>
		<head>
		<meta charset='utf-8'>
		<title>{$title}</title>
		<style type='text/css'>
		.title {background-color : #000000;color : #FFFF00;font-family : Microsoft YaHei;font-size : 24pt;margin : 0;padding : 0;text-align : center;}
		.null {background-color : #000000;color : #FFFFFF;font-family : SimSun;font-size : 10pt;margin : 0;padding : 9px;}
		.class0 {background-color : #000000;color : #FFFFFF;font-family : SimSun;font-size : 10pt;margin : 0;padding : 0;text-indent : 16px;text-align : left;}
		.class1 {background-color : #000000;color : #00FF00;font-family : SimSun;font-size : 10pt;margin : 0;padding : 0;text-indent : 16px;text-align : left;}
		.class2 {background-color : #000000;color : #FF8000;font-family : SimSun;font-size : 10pt;margin : 0;padding : 0;text-indent : 16px;text-align : left;}
		.class3 {background-color : #000000;color : #FF0000;font-family : SimSun;font-size : 10pt;margin : 0;padding : 0;text-indent : 16px;text-align : left;}
		</style>
		</head>
		<body>
		<p class='null'></p>
		<p class='title'>{$title}</p>
		<p class='null'></p>
		{$data}
		</body>
		</html>
	]]
	end
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 添加日志内容 [LogText:日志内容, Level:等级(可选)]
function QMPlugin.HLog_AddLog(LogText, Level)
	sh_init()
	local iRet, sRet = pcall(function()
		-- 初始化模版
		if isInit then 
			Init()
			isInit = false
		end
		local tmpLevel, tmpCode, tmpData
		tmpLevel = Html.HtmlLevel
		tmpPage  = Html.TempletPage
		if Level ~= nil then 
			tmpLevel = Level
		end
		-- 单条日志
		tmpData  = string.gsub(Html.TempletItem, "{$level}", tmpLevel)
		tmpData  = string.gsub(tmpData, "{$data}", os.date("[%H:%M:%S] ")..LogText)
		HtmlData = HtmlData..tmpData.."\n"
		-- 网页模版
		tmpPage  = string.gsub(Html.TempletPage, "{$title}", Html.HtmlTitle)
		tmpPage  = string.gsub(tmpPage, "{$data}", HtmlData)
		HtmlPage = tmpPage
		if isAutoSave then
			-- 写入日志
			WriteFile(Html.HtmlPath, HtmlPage, "w")
		end
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 保存日志 [path:保存路径(可选)]
function QMPlugin.HLog_Save(path)
	sh_init()
	local iRet, sRet = pcall(function()
		Init()
		if Html.HtmlPath == nil then
			Html.HtmlPath = TempPath().."log_"..os.date("%Y%m%d%H%M")..".html"
		end
		if path ~= nil then
			WriteFile(path, HtmlPage, "w")
		else
			WriteFile(Html.HtmlPath, HtmlPage, "w")
		end
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

-- 设置是否自动保存 [mode:true自动保存]
function QMPlugin.HLog_AutoSave(mode)
	if mode then
		isAutoSave = mode
	end
end

-- 设置模版 [tbl:包含模版内容的table数据]
function QMPlugin.HLog_SetTemplet(tbl)
	Html.HtmlLevel   = tbl.HtmlLevel
	Html.TempletItem = tbl.TempletItem
	Html.HtmlPath    = tbl.HtmlPath
	Html.HtmlTitle   = tbl.HtmlTitle
	Html.TempletPage = tbl.TempletPage
end

-- 获取模版内容 
function QMPlugin.HLog_GetTemplet()
	Init()
	return Html
end











--
---------------------------------------------- 文本日志函数 ----------------------------------------------
--

--设置日志文件路径 
local Txt_log = {}
function QMPlugin.TLog_LogPath(path, sign)
	sh_init()
	if sign == nil then sign = 0 end
	Txt_log[sign] = path
end 
--日志记录 
function QMPlugin.TLog_OutLog(msg, sign)
	if Txt_log[sign] == nil then sign = 0 end
	local t = os.date("[%H:%M:%S]")
	local f = io.open(Txt_log[sign], "a")
	assert(f, "日志路径错误")
	f:write(t.. "\t"..msg.."\r\n")
    f:close()
end











--
---------------------------------------	LUA正则模式匹配	---------------------------------------
--

--全局正则匹配[包含单子串]
function QMPlugin.RegexFind(str,pattern)
	sh_init()
	local iRet, sRet = pcall(function()
	local ret ={}
	for v in string.gmatch(str,pattern) do 
		table.insert(ret,v)
	end 
	return ret
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return arr
	end
end 

--全局正则匹配多子串 
function QMPlugin.RegexFindEx(str,pattern)
	sh_init()
	local t1 = {}
	local i=1
	local ePos
	repeat
		local ta = {string.find(str, pattern, ePos)}
		ePos = ta[2]
		if ta[1] ~= nil then
			t1[i] = ta
			table.remove(t1[i],1)
			table.remove(t1[i],1)
			i=i+1
		end
	until ta[1]==nil
	return t1
end











--
---------------------------------------	其他命令	---------------------------------------
--
--iif判断

--逻辑等价运算[位运算]
function QMPlugin.Eqv(t1, t2)
	sh_init()
	if type(t1) == "boolean" and type(t2) == "boolean" then
		if t1 == t2 then
			return true
		else
			return false
		end
	else
		local i1, i2, ir
		i1 = tonumber(t1)
		i2 = tonumber(t2)
		ir = 0
		for i=0, 31 do
			local b1 = band(2^i, i1)
			local b2 = band(2^i, i2)
			if b1 == b2 then
				ir = bit32.bor(ir, 2^i)
			end
		end
		return ir
	end
end

-- 返回算式计算结果 [expr:计算公式] [返回计算结果]
function QMPlugin.Eval(expr)
	sh_init()
	local iRet, sRet = pcall(function()
		f = load('return ' .. expr)
		return f()
	end)
	if iRet == true then
		return sRet
	else
		print(sRet)
		return ""
	end
end

--两点经纬度之间的直线距离  [作者：小玮]
function QMPlugin.GetDistance(lat1,lng1,lat2,lng2)
	sh_init()
	local rad = function(d) return d*math.pi/180 end
	local radLat1 = rad(lat1)
	local radLat2 = rad(lat2)
	return math.floor(2*math.asin(math.sqrt(math.pow(math.sin((radLat1-radLat2)/2),2)+math.cos(radLat1)*math.cos(radLat2)*math.pow(math.sin((rad(lng1) - rad(lng2))/2),2)))*6378.137*10000)/10000
--地球是一个近乎标准的椭球体,此处地球半径我们选择的是6378.137km
end












local json = {}

-- JSON 编码数据
function json.encode(v)
    if v==nil then return "null" end
    local vtype = type(v)
    -- 字符串
    if vtype=='string' then return '"' .. json_encodeString(v) .. '"' end
    -- 数学、逻辑型
    if vtype=='number' or vtype=='boolean' then return tostring(v) end
    -- 表
    if vtype=='table' then
        local rval = {}
        -- 判断表是否为数组
        local bArray, maxCount = json_isArray(v)
        if bArray then
            for i = 1, maxCount do table.insert(rval, json.encode(v[i])) end
        else
                for i, j in pairs(v) do
                if json_isEncodable(i) and json_isEncodable(j) then table.insert(rval, '"' .. json_encodeString(i) .. '":' .. json.encode(j)) end
            end
        end
        if bArray then
            return '[' .. table.concat(rval,',') ..']'
        else
            return '{' .. table.concat(rval,',') .. '}'
        end
    end
    -- 函数型(不支持)
    if vtype=='function' then return "null" end
    -- 类型断言
    assert(false, "JSON编码时发现了不支持的数据类型 " .. vtype .. ":" .. tostring(v))
end

-- JSON 解码数据
function json.decode(s, startPos)
    startPos = startPos and startPos or 1
    startPos = decode_scanWhitespace(s, startPos)
    assert(startPos <= string.len(s), "数据遍历位置超过结尾 [" .. s .. "]")
    local curChar = string.sub(s, startPos, startPos)
    -- 对象
    if curChar=='{' then return decode_scanObject(s, startPos) end
    -- 数组
    if curChar=='[' then return decode_scanArray(s, startPos) end
    -- 数字
    if string.find("+-0123456789.e", curChar, 1, true) then return decode_scanNumber(s, startPos) end
    -- 字符串
    if curChar==[["]] or curChar==[[']] then return decode_scanString(s, startPos) end
    -- 注释
    if string.sub(s,startPos,startPos+1)=='/*' then return decode(s, decode_scanComment(s, startPos)) end
    -- 常数
    return decode_scanConstant(s, startPos)
end

-- 扫描数组 [s:JSON串, startPos:扫描位置] [1:还原的表, 2:新的扫描位置]
function decode_scanArray(s, startPos)
    local array = {}
    local stringLen = string.len(s)
    assert(string.sub(s, startPos, startPos)=='[','扫描的位置不是数组数据的起点，位置：' .. startPos .. '\nJSON字符串：'..s)
    startPos = startPos + 1
    repeat
        startPos = decode_scanWhitespace(s,startPos)
        assert(startPos<=stringLen, 'JSON字符串扫描已结束，但没有找到数组结尾。')
        local curChar = string.sub(s, startPos, startPos)
        if (curChar==']') then return array, startPos + 1 end
        if (curChar==',') then startPos = decode_scanWhitespace(s,startPos+1) end
        assert(startPos<=stringLen, 'JSON字符串扫描已结束，但没有找到数组结尾。')
        object, startPos = json.decode(s,startPos)
        table.insert(array,object)
    until false
end

-- 扫描注释 [s:JSON串, startPos:扫描位置] [新的扫描位置]
function decode_scanComment(s, startPos)
    assert(string.sub(s, startPos, startPos + 1)=='/*', "扫描的位置不是注释数据的起点，位置：" .. startPos .. '\nJSON字符串：'..s)
    local endPos = string.find(s, '*/', startPos + 2)
    assert(endPos ~= nil, "JSON字符串扫描已结束，但没有找到注释结尾。")
    return endPos + 2
end

-- 扫描常数 [s:JSON串, startPos:扫描位置] [1:还原的数据, 2:新的扫描位置]
function decode_scanConstant(s, startPos)
    local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
    local constNames = {"true","false","null"}
    for i, k in pairs(constNames) do
        if string.sub(s,startPos, startPos + string.len(k) -1 ) == k then return consts[k], startPos + string.len(k) end
    end
    assert(nil, '无法找到标识符对应的值：' .. s .. '，位置：' .. startPos)
end

-- 扫描数字 [s:JSON串, startPos:扫描位置] [1:还原的数字, 2:新的扫描位置]
function decode_scanNumber(s, startPos)
    local endPos = startPos+1
    local stringLen = string.len(s)
    local acceptableChars = "+-0123456789.e"
    while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) and endPos<=stringLen) do endPos = endPos + 1 end
    local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
    local stringEval = load(stringValue)
    assert(stringEval, '扫描数字失败，' .. stringValue .. '在JSON字符串的位置：' .. startPos .. ' : ' .. endPos)
    return stringEval(), endPos
end

-- 扫描对象 [s:JSON串, startPos:扫描位置] [1:还原的表, 2:新的扫描位置]
function decode_scanObject(s, startPos)
    local object = {}
    local stringLen = string.len(s)
    local key, value
    assert(string.sub(s, startPos, startPos) == '{', '扫描的位置不是对象数据的起点，位置：' .. startPos .. '\nJSON字符串：'..s)
    startPos = startPos + 1
    repeat
        startPos = decode_scanWhitespace(s,startPos)
        assert(startPos<=stringLen, 'JSON字符串扫描已结束，但没有找到对象结尾。')
        local curChar = string.sub(s,startPos,startPos)
        if (curChar=='}') then return object,startPos+1 end
        if (curChar==',') then startPos = decode_scanWhitespace(s,startPos+1) end
        assert(startPos<=stringLen, 'JSON字符串扫描已结束，但没有找到对象结尾。')
        key, startPos = json.decode(s,startPos)
        assert(startPos<=stringLen, 'JSON字符串扫描已结束，但没有找到键值：' .. key)
        startPos = decode_scanWhitespace(s,startPos)
        assert(startPos<=stringLen, 'JSON字符串扫描已结束，但没有找到键值：' .. key)
        assert(string.sub(s,startPos,startPos)==':','JSON对象键值格式异常，错误位置：' .. startPos)
        startPos = decode_scanWhitespace(s,startPos+1)
        assert(startPos<=stringLen, 'JSON字符串扫描已结束，但没有找到键值：' .. key)
        value, startPos = json.decode(s,startPos)
        object[key]=value
    until false
end

-- 扫描字符串 [s:JSON串, startPos:扫描位置] [1:还原的字符串, 2:新的扫描位置]
local escapeSequences = {["\\t"] = "\t", ["\\f"] = "\f", ["\\r"] = "\r", ["\\n"] = "\n", ["\\b"] = "\b"}
setmetatable(escapeSequences, {__index = function(t, k) return string.sub(k,2) end})
function decode_scanString(s, startPos)
    assert(startPos, '扫描字符串时没有填写起始位置。')
    local startChar = string.sub(s, startPos, startPos)
    assert(startChar == [["]] or startChar == [[']], '非法的字符串格式。')
    local t = {}
    local i, j = startPos, startPos
    while string.find(s, startChar, j+1) ~= j+1 do
        local oldj = j
        i, j = string.find(s, "\\.", j+1)
        local x, y = string.find(s, startChar, oldj+1)
        if not i or x < i then i, j = x, y-1 end
        table.insert(t, string.sub(s, oldj+1, i-1))
        if string.sub(s, i, j) == "\\u" then
            local a = string.sub(s, j+1, j+4)
            j = j + 4
            local n = tonumber(a, 16)
            assert(n, "字符串UNICODE转译失败，" .. a .. " 无法转换为数字，出错位置：" .. i .. " : " .. j)
            local x
            if n < 0x80 then
                x = string.char(n % 0x80)
            elseif n < 0x800 then    -- [110x xxxx] [10xx xxxx]
                x = string.char(0xC0 + (math.floor(n/64) % 0x20), 0x80 + (n % 0x40))
            else                    -- [1110 xxxx] [10xx xxxx] [10xx xxxx]
                x = string.char(0xE0 + (math.floor(n/4096) % 0x10), 0x80 + (math.floor(n/64) % 0x40), 0x80 + (n % 0x40))
            end
            table.insert(t, x)
        else
            table.insert(t, escapeSequences[string.sub(s, i, j)])
        end
    end
    table.insert(t,string.sub(j, j+1))
    assert(string.find(s, startChar, j+1), "字符串扫描失败，找不到关闭符号。出错位置：开始(" .. startChar .. ") 结束(" .. startPos .. ")")
    return table.concat(t,""), j+2
end

-- 扫描字符串返回下一个非白字符 [空格、Tab、换行、回车]
function decode_scanWhitespace(s,startPos)
    local whitespace=" \n\r\t"
    local stringLen = string.len(s)
    while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)  and startPos <= stringLen) do startPos = startPos + 1 end
    return startPos
end

-- 返回可以编码的字符串
local json_escapeList = {['"']  = '\\"', ['\\'] = '\\\\', ['/']  = '\\/',  ['\b'] = '\\b', ['\f'] = '\\f', ['\n'] = '\\n', ['\r'] = '\\r', ['\t'] = '\\t'}
function json_encodeString(s)
    local s = tostring(s)
    return s:gsub(".", function(c) return json_escapeList[c] end)
end

-- 判断一个表是否为数组
function json_isArray(t)
    local maxIndex = 0
    for k, v in pairs(t) do
        if (type(k)=='number' and math.floor(k)==k and 1<=k) then
            if (not json_isEncodable(v)) then return false end
            maxIndex = math.max(maxIndex,k)
        else
            if (k=='n') then
                if v ~= table.getn(t) then return false end
            else
                if json_isEncodable(v) then return false end
            end
        end
    end
    return true, maxIndex
end

-- 判断数据是否可编码
function json_isEncodable(o)
    local t = type(o)
    return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null)
end

QMPlugin.Json_Encode = function(v)
	local iRet, sRet = pcall(function()
		return json.encode(v)
	end)
	if iRet == true then
		return sRet
	else
		return nil
	end
end


QMPlugin.Json_Decode = function(v)
	local iRet, arrRet = pcall(function()
		return json.decode(v, 0)
	end)
	if iRet == true then
		return arrRet
	else
		return nil
	end
end



